﻿#region File and License Information
/*
<File>
	<Copyright>Copyright © 2009, Daniel Vaughan. All rights reserved.</Copyright>
	<License>
	This file is part of DanielVaughan's base library

    DanielVaughan's base library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DanielVaughan's base library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with DanielVaughan's base library.  If not, see http://www.gnu.org/licenses/.
	</License>
	<Owner Name="Daniel Vaughan" Email="dbvaughan@gmail.com"/>
	<CreationDate>2010-02-06 18:16:45Z</CreationDate>
</File>
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace DanielVaughan.TaskModel
{
	/// <summary>
	/// Provides the ability to group tasks for execution
	/// either sequentially or in parallel. <see cref="CompositeTask.Parallel"/>
	/// </summary>
	public class CompositeTask<T> : TaskBase<T>
	{
		readonly string descriptionForUser;
		readonly Dictionary<TaskBase<T>, T> taskDictionary;

		public CompositeTask(IDictionary<TaskBase<T>, T> tasks, string descriptionForUser)
		{
			ArgumentValidator.AssertNotNull(descriptionForUser, "descriptionForUser");
			ArgumentValidator.AssertNotNull(tasks, "tasks");
			this.descriptionForUser = descriptionForUser;
			taskDictionary = new Dictionary<TaskBase<T>, T>(tasks);
			Execute += OnExecute;

			/* Determine repeatable status. */
			bool repeatable = taskDictionary.Keys.Count > 0;
			foreach (IInternalTask key in tasks.Keys)
			{
				if (!key.Repeatable)
				{
					repeatable = false;
				}
			}
			Repeatable = repeatable;
		}

		void OnExecute(object sender, TaskEventArgs<T> e)
		{
			ExecuteInternal(taskDictionary);
		}

		protected internal virtual void ExecuteInternal(Dictionary<TaskBase<T>, T> taskDictionary)
		{
			if (Parallel)
			{
				ExecuteInParallel(taskDictionary);
			}
			else
			{
				ExecuteInSequence(taskDictionary);
			}
		}

		public override string DescriptionForUser
		{
			get
			{
				return descriptionForUser;
			}
		}

		#region Parallel Execution
		/// <summary>
		/// Gets or sets a value indicating whether the composite tasks
		/// are to be performed sequentially, one after the other, or in parallel.
		/// </summary>
		/// <value><c>true</c> if parallel; otherwise, <c>false</c>.</value>
		public bool Parallel { get; set; }

		static void ExecuteInSequence(Dictionary<TaskBase<T>, T> taskDictionary)
		{
			foreach (KeyValuePair<TaskBase<T>, T> pair in taskDictionary)
			{
				var task = (IInternalTask)pair.Key;
				task.PerformTask(pair.Value);
			}
		}

		static void ExecuteInParallel(Dictionary<TaskBase<T>, T> taskDictionary)
		{
			/* When we move to .NET 4 we may use System.Threading.Parallel for the Desktop CLR. */
			var performedTasks = new List<TaskBase<T>>();
			object performedTasksLock = new object();
			var exceptions = new List<Exception>();
			object exceptionsLock = new object();
			var events = taskDictionary.ToDictionary(x => x, x => new AutoResetEvent(false));

			foreach (KeyValuePair<TaskBase<T>, T> pair in taskDictionary)
			{
				var autoResetEvent = events[pair];
				var task = (IInternalTask)pair.Key;
				var undoableTask = pair.Key;
				var arg = pair.Value;

				ThreadPool.QueueUserWorkItem(
					delegate
					{
						try
						{
							task.PerformTask(arg);
							lock (performedTasksLock)
							{
								performedTasks.Add(undoableTask);
							}
						}
						catch (Exception ex)
						{
							/* TODO: improve this to capture undone task errors. */
							lock (exceptionsLock)
							{
								exceptions.Add(ex);
							}
						}
						autoResetEvent.Set();
					});

			}

			foreach (var autoResetEvent in events.Values)
			{
				autoResetEvent.WaitOne();
			}

			if (exceptions.Count > 0)
			{
				throw new CompositeException("Unable to undo tasks", exceptions);
			}
		}
		#endregion
	}
}
